package spring.cloud.tasks.executor.task;


import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import spring.cloud.tasks.executor.utils.log.LogUtils;

import java.lang.Thread.UncaughtExceptionHandler;
import java.util.concurrent.Callable;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledFuture;

@Slf4j
@Setter
@Getter
public class TaskItemExecuteTask implements Callable<TaskItemExecuteResult> {
    private TaskItemExecute taskItemExecute;
    private Future<?> future;
    private Callable<?> doneFinallyCallbackCallable;
    private ScheduledFuture<?> timeoutScheduledFuture;
    private boolean done = false;

    public TaskItemExecuteTask(TaskItemExecute taskItemFutureTaskCaller, Callable<?> doneFinallyCallbackCallable) {
        this.taskItemExecute = taskItemFutureTaskCaller;
        this.doneFinallyCallbackCallable = doneFinallyCallbackCallable;
    }

    public static void killRunningBusinessThread(TaskItemExecuteTask taskItemFutureTask) {
        TaskItemExecute taskItemFutureTaskCaller = taskItemFutureTask.getTaskItemExecute();
        Thread currentThread = taskItemFutureTaskCaller.getCurrentThread();
        if (currentThread != null) {
            try {
                // interrupt thread one time, wait business thread to break, wait 2000ms at most
                if (!isBusinessBreak(taskItemFutureTask, taskItemFutureTaskCaller)) {
                    LogUtils.info(log, taskItemFutureTaskCaller.getTaskId(), "try to interrupt business thread");
                    currentThread.interrupt();
                    for (int i = 0; i < 20; i++) {
                        if (isBusinessBreak(taskItemFutureTask, taskItemFutureTaskCaller)) {
                            LogUtils.info(log, taskItemFutureTaskCaller.getTaskId(), "interrupt business thread done");
                            return;
                        }
                        Thread.sleep(100L);
                    }
                }
                // stop thread
                while (!isBusinessBreak(taskItemFutureTask, taskItemFutureTaskCaller)) {
                    LogUtils.info(log, taskItemFutureTaskCaller.getTaskId(), "try to force stop business thread");
                    currentThread.stop();
                    if (isBusinessBreak(taskItemFutureTask, taskItemFutureTaskCaller)) {
                        LogUtils.info(log, taskItemFutureTaskCaller.getTaskId(), "force stop business thread done");
                        return;
                    }
                    Thread.sleep(50L);
                }
                LogUtils.info(log, taskItemFutureTaskCaller.getTaskId(), "kill business thread done");
            } catch (InterruptedException e) {// NOSONAR
            }
        } else {
            LogUtils.warn(log, taskItemFutureTaskCaller.getTaskId(), "business thread is null while killing it");
        }
    }

    private static boolean isBusinessBreak(TaskItemExecuteTask taskItemFutureTask, TaskItemExecute taskItemFutureTaskCaller) {
        return taskItemFutureTaskCaller.isBreakForceStop() || taskItemFutureTask.isDone();
    }


    public void reset() {
        done = false;
        taskItemExecute.reset();
    }

    @Override
    public TaskItemExecuteResult call() throws Exception {
        Thread.currentThread().setUncaughtExceptionHandler(new UncaughtExceptionHandler() {
            @Override
            public void uncaughtException(Thread t, Throwable e) {
                if (e instanceof IllegalMonitorStateException || e instanceof ThreadDeath) {
                    LogUtils.warn(log, taskItemExecute.getTaskId(), "business thread pool maybe crashed", e);
                    if (future != null) {
                        future.cancel(false);
                    }
                    LogUtils.warn(log, taskItemExecute.getTaskId(),
                            "close the old business thread pool, and re-create new one");
                    taskItemExecute.getTaskExecuteEngine().getTaskExecutor().reCreateExecutorService();
                }
            }

        });
        try {
            TaskItemExecuteResult executeResult = taskItemExecute.call();
            return executeResult;
        } finally {
            done();
            LogUtils.debug(log, taskItemExecute.getTaskId(), "job:[{}] item:[{}] finish execution, which takes {}ms",
                    taskItemExecute.getTaskId(), taskItemExecute.getTaskItemIndex(), taskItemExecute.getExecutionTime());
        }
    }

    private void done() {
        if (timeoutScheduledFuture != null) {
            timeoutScheduledFuture.cancel(true);
            timeoutScheduledFuture = null;
        }

        if (done) {
            return;
        }
        done = true;
        try {
            try {
                if (taskItemExecute.isTimeout()) {
                    taskItemExecute.onTimeout();
                }
            } catch (Throwable t) {
                LogUtils.error(log, taskItemExecute.getTaskId(), t.toString(), t);
            }

            try {
                if (taskItemExecute.isForceStop()) {
                    taskItemExecute.postForceStop();
                }
            } catch (Throwable t) {
                LogUtils.error(log, taskItemExecute.getTaskId(), t.toString(), t);
            }

            taskItemExecute.checkAndSetSaturnJobReturn();

            taskItemExecute.afterExecution();

        } finally {
            try {
                if (doneFinallyCallbackCallable != null) {
                    doneFinallyCallbackCallable.call();
                }
            } catch (Exception e) {
                LogUtils.error(log, taskItemExecute.getTaskId(), e.toString(), e);
            }
        }
    }
}
